﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Web.Mvc;
using System.Xml.Linq;

namespace WB.Mvc.Forms
{
	/// <summary>
	/// Base class for creating html forms.
	/// </summary>
	[Serializable]
	public abstract class FormBase
	{
		private readonly IList<FieldGroup> fieldGroups;
		private string action;
		private string buttonText;
		private string errorCssClass = "error";
		private string legend;
		private FormMethod method;

		/// <summary>
		/// Creates an instance of <see cref="FormBase"/> with
		/// specified values for fields and verifies that the values 
		/// are valid.
		/// </summary>
		protected FormBase(string name)
		{
			fieldGroups = new List<FieldGroup> {new FieldGroup {Name = name}};
			action = string.Empty;
			method = FormMethod.Post;
			legend = name.ToHumanReadable();
			buttonText = string.Format("Submit {0}", name.ToHumanReadable());
		}

		/// <summary>
		/// Groups of fields that make up the form.
		/// </summary>
		/// <remarks>
		/// A default field group is provided by the constructor.
		/// </remarks>
		public virtual IList<FieldGroup> FieldGroups
		{
			get { return fieldGroups; }
		}

		/// <summary>
		/// Fields in the form.
		/// </summary>
		public virtual IEnumerable<Field> Fields
		{
			get
			{
				return from grp in fieldGroups
				       from field in grp.Fields
				       select field;
			}
		}

		/// <summary>
		/// The action for the form.
		/// </summary>
		public virtual string Action
		{
			get { return action; }
			set { action = value; }
		}

		/// <summary>
		/// The method for the form.
		/// </summary>
		public virtual FormMethod Method
		{
			get { return method; }
			set { method = value; }
		}

		/// <summary>
		/// The legend for the form.
		/// </summary>
		public virtual string Legend
		{
			get { return legend; }
			set { legend = value; }
		}

		/// <summary>
		/// The CSS class to put on a field with an invalid value.
		/// </summary>
		public virtual string ErrorCssClass
		{
			get { return errorCssClass; }
			set { errorCssClass = value; }
		}

		/// <summary>
		/// Text for the submit button.
		/// </summary>
		public virtual string ButtonText
		{
			get { return buttonText; }
			set { buttonText = value; }
		}

		/// <summary>
		/// Load field values from a <see cref="NameValueCollection"/>.
		/// This method triggers validation.
		/// </summary>
		public virtual void Load(NameValueCollection form)
		{
			if (form != null)
			{
				foreach (string fieldName in form.Keys)
				{
					if (String.IsNullOrEmpty(fieldName))
						continue;

					Field field = Fields.SingleOrDefault(x => x.Name.Equals(fieldName, StringComparison.InvariantCultureIgnoreCase));
					if (field == null)
						continue;

					field.Value = form[fieldName];
				}

				Validate();
			}
		}

		/// <summary>
		/// Template based render method.
		/// </summary>
		/// <param name="fieldGroupFactory">Returns element to which the fields of a field group will belong.</param>
		protected virtual XElement Render(Func<XElement[], XElement> fieldGroupFactory, Func<XElement[], XElement> fieldFactory)
		{
			var fieldGroupElements = new List<XElement>(FieldGroups.Count);

			foreach (FieldGroup fieldGroup in FieldGroups)
			{
				var fieldElements = new List<XElement>(fieldGroup.Fields.Count);

				foreach (Field field in fieldGroup.Fields)
				{
					XElement fieldElement = fieldFactory(field.ToXml());
					errorCssClass = "error";
					if (!field.IsValid)
						fieldElement.Add(new XAttribute("class", errorCssClass));

					fieldElements.Add(fieldElement);
				}

				XElement fieldGroupElement = fieldGroupFactory(fieldElements.ToArray());

				fieldGroupElements.Add(fieldGroupElement);
			}

			var submitButton = new XElement("input", new XAttribute("type", "submit"), new XAttribute("value", ButtonText));


			var form = new XElement("form", new XAttribute("action", Action),
			                        new XAttribute("method", Method.ToString().ToLower()));
			form.Add(submitButton);
			fieldGroupElements.ForEach(form.Add);

			return form;
		}

		/// <summary>
		/// Returns the form with each field's elements as children of 
		/// <paramref name="fieldTemplate"/>.  
		/// </summary>
		/// <param name="fieldGroupTemplate">Element that will wrap a field group.</param>
		/// <param name="fieldTemplate">Element that will contain the field's contents.</param>
		public virtual string AsCustom(XElement fieldGroupTemplate, XElement fieldTemplate)
		{
			return Render(
				(elements) =>
					{
						XElement template = XElement.Load(fieldGroupTemplate.CreateReader());

						template.Add(elements);
						return template;
					},
				(elements) =>
					{
						// Copy template.
						XElement template = XElement.Load(fieldTemplate.CreateReader());

						template.Add(elements);
						return template;
					}).ToString();
		}

		/// <summary>
		/// Returns the form with each field wrapped inside of a div.
		/// </summary>
		public virtual string AsDiv()
		{
			var div = new XElement("div");
			var fieldset = new XElement("fieldset");

			return AsCustom(fieldset, div);
		}

		/// <summary>
		/// Returns the form as a table.
		/// </summary>
		public virtual string AsTable()
		{
			var wrapper = new XElement("table");

			Func<XElement[], XElement> fieldFactory =
				(elements) =>
					{
						var tr = new XElement("tr");
						foreach (XElement ele in elements)
							tr.Add(new XElement("td", ele));
						return tr;
					};

			return Render((elements) =>
			              	{
			              		var table = new XElement("table");	
			              		elements.ToList().ForEach(table.Add);

								var fieldset = new XElement("fieldset");
								fieldset.Add(table);

			              		return fieldset;
			              	}, fieldFactory).ToString();
		}

		/// <summary>
		/// Returns the form rendered as an unordered list.
		/// </summary>
		public virtual string AsList()
		{
			var wrapper = new XElement("ul");

			return AsCustom(wrapper, new XElement("li"));
		}

		/// <summary>
		/// Validates the fields in this <see cref="FormBase"/>.
		/// </summary>
		protected virtual void Validate()
		{
			foreach (Field field in Fields)
				field.Validate();
		}

		/// <summary>
		/// Returns <c>true</c> if all the fields in the form have valid values, <c>false</c> 
		/// if they do not.
		/// If <see cref="Validate"/> has not been called, this will always return <c>true</c>.
		/// </summary>
		public virtual bool IsValid()
		{
			foreach (Field field in Fields)
				if (!field.IsValid)
					return false;

			return true;
		}
	}
}